﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public class ItemInf
    {
        public int ID;
        public double height;
        public double bust;
        public double waist;
        public double hipline;
        public string size=new("");
        //扩展数据
        public double dExtend1;  //后三围
        public double dExtend2;  //衣长
        public double dExtend3;  //肩宽
        public double dExtend4;  //袖长
        public string sExtend5;  //备注
    }
    public partial class MainWindow : Window
    {
        List<ItemInf>[] clusterList = new List<ItemInf>[9];    //每一次聚类的结果，共九个类
        List<ItemInf> sourceData;
        ItemInf[] stdData = new ItemInf[9];    //K-means的质点，第一次计算存储标准号型数据
        ItemInf[] testData;    //测试数据集
        double XrayOffset, YrayOffset,XrayScale,YrayScale; //X-bust Y-Height  用于计算显示输出
        Label[] gridResTable = new Label[36]; //4*9    //聚类结果的表格
        struct standardHandle
        {
            public double height=0;
            public double bust=0;
        };
        standardHandle stdAvr;
        standardHandle stdBuffer;
        public MainWindow()
        {
            InitializeComponent();
            //初始化结果表格
            int gridResTablePtr = 0;
            for (int i = 0; i < 9; i++)
            {
                for (int j = 0; j < 4; j++)
                {
                    gridResTable[i * 4 + j] = new Label();
                    gridResTable[i * 4 + j].HorizontalAlignment = HorizontalAlignment.Center;
                    gridResTable[i * 4 + j].VerticalAlignment = VerticalAlignment.Center;
                    Grid.SetRow(gridResTable[i * 4 + j], i + 2);
                    Grid.SetColumn(gridResTable[i * 4 + j], j + 1);
                    gridClusteringRes.Children.Add(gridResTable[i * 4 + j]);
                    //Debug.WriteLine(i * 4 + j);
                }
            }
            //初始化结果
            for (int i = 0; i < 9; i++)
                clusterList[i] = new List<ItemInf>();
        }
        
        private void btnOpenFile_Click(object sender, RoutedEventArgs e)
        {
            //按钮事件处理韩式
            //打开文件，调用readDataFromFile函数
            //已设置过滤器(txt)
            Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog();
            openFileDialog.DefaultExt = ".txt";
            openFileDialog.Filter = "Text documents (.txt)|*.txt";
            Nullable<bool> result = openFileDialog.ShowDialog();
            if (result == true)
            {
                labStatusInf.Content = "文件打开成功" + openFileDialog.FileName;
                readDataFromFile(openFileDialog.FileName);
                return;
            }
            else
            {
                MessageBox.Show("未打开文件");
            }

        }

        private void DataStandard()
        {
            //均值
            stdAvr.height = 0;
            stdAvr.bust = 0;
            foreach(var item in sourceData)
            {
                stdAvr.height += item.height;
                stdAvr.bust += item.bust;
            }
            //Debug.WriteLine(stdAvr.height);
            //Debug.WriteLine(stdAvr.bust);
            //Debug.WriteLine(stdAvr.waist);
            //Debug.WriteLine(stdAvr.hipline);
            stdAvr.height/=sourceData.Count;
            stdAvr.bust/=sourceData.Count;
            
            Label newLab1=new Label();
            Grid.SetRow(newLab1, 1);
            Grid.SetColumn(newLab1, 2);
            newLab1.Content = stdAvr.height;
            gridDataInf.Children.Add(newLab1);

            Label newLab2 = new Label();
            Grid.SetRow(newLab2, 2);
            Grid.SetColumn(newLab2, 2);
            newLab2.Content = stdAvr.bust;
            gridDataInf.Children.Add(newLab2);

            //总体标准差
            stdBuffer.bust = stdBuffer.height = 0;
            foreach(var item in sourceData)
            {
                stdBuffer.height+=Math.Pow(item.height-stdAvr.height,2);
                stdBuffer.bust+=Math.Pow(item.bust-stdAvr.bust,2);
            }
            Label newLab5 = new Label();
            Grid.SetRow(newLab5, 1);
            Grid.SetColumn(newLab5, 1);
            stdBuffer.height = Math.Sqrt(stdBuffer.height / sourceData.Count);
            newLab5.Content = stdBuffer.height;
            gridDataInf.Children.Add(newLab5);

            Label newLab6 = new Label();
            Grid.SetRow(newLab6, 2);
            Grid.SetColumn(newLab6, 1);
            stdBuffer.bust = Math.Sqrt(stdBuffer.bust / sourceData.Count);
            newLab6.Content = stdBuffer.bust;
            gridDataInf.Children.Add(newLab6);

            //z-souce
            for (int i = 0; i < sourceData.Count;i++)    //O(n)=n^2 !!! 来自C++开发者的愤怒
            {
                sourceData[i].height = (sourceData[i].height - stdAvr.height) / stdBuffer.height;
                sourceData[i].bust = (sourceData[i].bust -stdAvr.bust) / stdBuffer.bust;
                //Debug.WriteLine(sourceData[i].height);
            }
        }
        private void DrawingData(ItemInf[] listA,Color colors,int zIndex = 0 )
            //将数据绘制成散点图,X为itemInf.bust Y为itemInf.height，其他数据成员没有参与
            //listA: 数据条目
            //colors: 数据颜色
            //zIndex: 数据图层（Canvas.SetZIndex)
        {
            foreach(ItemInf itemInf in listA)
            {
                Ellipse newEllipse = new();
                newEllipse.Fill = new SolidColorBrush(colors);
                newEllipse.Width = 5;
                newEllipse.Height = 5;
                Canvas.SetLeft(newEllipse, (itemInf.bust - XrayOffset) * (XrayScale/2));
                Canvas.SetBottom(newEllipse, (itemInf.height - YrayOffset) * (YrayScale/2));
                if((itemInf.bust - XrayOffset) * XrayScale < 0 || (itemInf.bust + XrayOffset) * XrayScale > 400)
                    //Debug.WriteLine((itemInf.bust + XrayOffset) * XrayScale);
                Canvas.SetZIndex(newEllipse, zIndex);
                cavDrawArea.Children.Add(newEllipse);
            }
        }
        private void readDataFromFile(String filePath)
        {
            //读取文件数据，文件应该是一个纯文本文件，一行六个值，前9行为参考数据，后面是要分类的数据
            //对于参考数据，ID,size不参与计算，可以填0，但不能忽略
            //每一列应该用空格或制表符隔开，每一行应该用换行符隔开
            //filePath: 文件路径
            //自动初始化sourceData和图形坐标系，并且在界面中绘制散点图
            sourceData = new List<ItemInf>();
            try
            {
                using (StreamReader reader = new StreamReader(filePath))
                {
                    ItemInf newInf;  //ItemInf newInf=new();
                    while (reader.Peek() != -1)
                    {
                        string line = reader.ReadLine();
                        int status = 0;
                        string buffer = "";
                        newInf = new ItemInf();
                        //对该行数据进行解析，格式应该为: int double double double double string
                        //每一列对应为: ID height bust waist hipline size
                        try
                        {
                            status = 0;
                            line += ' ';
                            foreach(char x in line)
                            {
                                if (x == '\t' && status<11)
                                {
                                    switch (status)
                                    {
                                        case 0:
                                            newInf.ID = Convert.ToInt32(buffer);
                                            break;
                                        case 1:
                                            newInf.height = Convert.ToDouble(buffer);
                                            break;
                                        case 2:
                                            newInf.bust = Convert.ToDouble(buffer);
                                            break;
                                        case 3:
                                            newInf.waist = Convert.ToDouble(buffer);
                                            break;
                                        case 4:
                                            newInf.hipline = Convert.ToDouble(buffer);
                                            break;
                                        case 5:
                                            newInf.size = buffer;
                                            break;
                                    }
                                    if (buffer.Length > 0)
                                    {
                                        switch (status)
                                        {
                                            //扩展数据
                                            case 6:
                                                newInf.dExtend1 = Convert.ToDouble(buffer);
                                                break;
                                            case 7:
                                                newInf.dExtend2 = Convert.ToDouble(buffer);
                                                break;
                                            case 8:
                                                newInf.dExtend3 = Convert.ToDouble(buffer);
                                                break;
                                            case 9:
                                                newInf.dExtend4 = Convert.ToDouble(buffer);
                                                break;
                                            case 10:
                                                newInf.sExtend5 = buffer;
                                                break;
                                        }
                                    }
                                    status++;
                                    buffer = "";
                                }
                                else
                                    buffer += x;
                            }
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show("数据有误 位于: "+status+" 值: "+buffer+" 位置: "+(sourceData.Count+1).ToString());
                            Debug.WriteLine(ex.Message);
                            return;
                        }
                        sourceData.Add(newInf);
                    }
                    labStatusInf.Content = "读取数据总计: " + sourceData.Count;
                }
            }
            catch (Exception ex)
            {
                labStatusInf.Content=ex.Message;
            }

            //初始化各个参数并输出
            DataStandard();
            double minVal = 0;
            double maxVal = 0;
            foreach (var x in sourceData)
            {
                if (minVal > x.height)
                    minVal = x.height;
                if (maxVal < x.height)
                    maxVal = x.height;
            }
            Label lab1 = new();
            Grid.SetRow(lab1, 1);
            Grid.SetColumn(lab1, 1);
            lab1.Content = minVal;
            gridRayInf.Children.Add(lab1);

            Label lab2 = new();
            Grid.SetRow(lab2, 1);
            Grid.SetColumn(lab2, 2);
            lab2.Content = maxVal;
            gridRayInf.Children.Add(lab2);
            YrayOffset = minVal;
            YrayScale = (cavDrawArea.Height-3) / maxVal;

            minVal = maxVal = 0;
            foreach (var x in sourceData)
            {
                if (minVal > x.bust)
                    minVal = x.bust;
                if (maxVal < x.bust)
                    maxVal = x.bust;
            }
            Label lab3 = new();
            Grid.SetRow(lab3, 2);
            Grid.SetColumn(lab3, 1);
            lab3.Content = minVal;
            gridRayInf.Children.Add(lab3);

            Label lab4 = new();
            Grid.SetRow(lab4, 2);
            Grid.SetColumn(lab4, 2);
            lab4.Content = maxVal;
            gridRayInf.Children.Add(lab4);
            XrayOffset = minVal;
            XrayScale = (cavDrawArea.Width-3) / maxVal;

            //将数据切分，前九条数据为质点，后面的数据是要分类的
            testData = new ItemInf[sourceData.Count - 9];
            for(int i = 0;i<9;i++)
            {
                stdData[i]=sourceData[i];
                //Debug.WriteLine(stdData[i].bust);
            }
            for(int i=9;i<sourceData.Count;i++)
            {
                testData[i-9]=sourceData[i];
            }
            DrawingData(stdData, Colors.Blue, 1);
            DrawingData(testData, Colors.Green, 0);
        }
        private void outputData()
            //将结果更新到视图，数据至少被calClustering函数处理一次
        {
            //Output Inf
            for (int i = 0; i < 9; i++)
            {
                double rrHeight = 0, rrBust = 0;
                foreach (var x in clusterList[i])
                {
                    rrHeight += x.height * stdBuffer.height + stdAvr.height;
                    rrBust += x.bust * stdBuffer.bust + stdAvr.bust;
                }
                rrHeight /= clusterList[i].Count;
                rrBust /= clusterList[i].Count;
                gridResTable[i * 4].Content = Math.Round(rrHeight, 1).ToString() + '/' + Math.Round(rrBust, 1).ToString();
                gridResTable[i * 4 + 1].Content = clusterList[i].Count;
                DrawingData(clusterList[i].ToArray(), Color.FromRgb((byte)(25 * i), (byte)(225 - 25 * i), (byte)150));
            }
            DrawingData(stdData, Colors.Black, 1);
            //T-test
            for(int i=0;i< 9;i++)
            {
                double avrHeight = 0,avrBust = 0;
                foreach(var x in clusterList[i])
                {
                    avrHeight += x.height;
                    avrBust += x.bust;
                }
                avrHeight/=clusterList[i].Count;
                avrBust/=clusterList[i].Count;
                double sumDiffHeight = 0,sumDiffBust = 0;
                foreach(var x in clusterList[i])
                {
                    sumDiffHeight = Math.Pow(x.height - avrHeight,2);
                    sumDiffBust = Math.Pow(x.bust - avrBust,2);
                }
                gridResTable[i*4+2].Content=Math.Round(sumDiffHeight/clusterList[i].Count,2).ToString()+'/'+Math.Round(sumDiffBust/clusterList[i].Count,2).ToString();
            }
        }
        private bool calClustering()
            //返回值：是否能继续聚类
        {
            for(int i=0;i<9;i++)
                clusterList[i].Clear();

            foreach (var i in testData)
            {
                double[] resDistance = new double[9];
                int resDisPos = 0;
                foreach (var j in stdData)
                {
                    //Euclidean距离
                    resDistance[resDisPos++] = Math.Sqrt(Math.Pow(i.height - j.height, 2) + Math.Pow(i.bust - j.bust, 2));
                }
                int minPos = 0;
                int isPos = 0;
                double minValue = resDistance.Min();
                foreach (double x in resDistance)
                {
                    if (x <= minValue)
                        minPos = isPos;
                    isPos++;
                }
                clusterList[minPos].Add(i);
            }

            bool returnRes = resCalParticle();
            return returnRes;
        }

        private void btnOutputResult_Click(object sender, RoutedEventArgs e)
        {
            using(StreamWriter sw=new StreamWriter("分类结果.txt"))
            {
                for(int i=0;i<9;i++)
                {
                    sw.WriteLine("{");
                    foreach(ItemInf x in clusterList[i])
                    {
                        string outputLine = "";
                        outputLine = x.ID.ToString() + '\t' +
                            x.height.ToString() + '\t' +
                            x.bust.ToString() + '\t' +
                            x.waist.ToString() + '\t' +
                            x.hipline.ToString() + '\t' +
                            x.size + '\t' +
                            x.dExtend1.ToString() + '\t' +
                            x.dExtend2.ToString() + '\t' +
                            x.dExtend3.ToString() + '\t' +
                            x.dExtend4.ToString();
                        sw.WriteLine(outputLine);
                    }
                    sw.WriteLine("}");
                }
            }
            labStatusInf.Content = "导出结果成功";
        }
        private bool resCalParticle()
            //返回值：和上一次质点是否相同
            //平均值法
        {
            ItemInf IIbuff=new();
            ItemInf[] itemInfs=new ItemInf[9];
            bool isIdentical = true;
            for(int i=0;i<9;i++)
            {
                IIbuff.height = IIbuff.bust = IIbuff.waist = IIbuff.hipline = 0;
                foreach (ItemInf x in clusterList[i])
                {
                    IIbuff.height += x.height;
                    IIbuff.bust += x.bust;
                }
                IIbuff.height/=clusterList[i].Count;
                IIbuff.bust/=clusterList[i].Count;
                itemInfs[i]=IIbuff;
                if (IIbuff.height != stdData[i].height)
                    isIdentical = false;
                else if (IIbuff.bust != stdData[i].bust)
                    isIdentical = false;
                stdData[i].height = IIbuff.height;
                stdData[i].bust = IIbuff.bust;
                //Debug.WriteLine("i="+i.ToString()+" "+itemInfs[i].height.ToString()+" "+itemInfs[i].bust.ToString());
            }
            return isIdentical;
        }
        private void btnNextDeal_Click(object sender, RoutedEventArgs e)
            //按钮事件处理函数
        {
            //每按下一次做一次聚类，并更新相关数据
            if (calClustering())
                labStatusInf.Content = "质点未发生变化";
            else
                labStatusInf.Content = "质点发生变化";

            outputData();
        }
        private void btnAllDeal_Click(object sender, RoutedEventArgs e)
            //计算出最终的聚类结果
        {
            int count = 0;
            while (!calClustering() && count < 100)
                count++;
            if (count >= 100) labStatusInf.Content = "尝试聚类100仍未稳定";
            else labStatusInf.Content = "聚类次数: " + count.ToString();
            cavDrawArea.Children.Clear();

            outputData();
        }


    }
}
